package com.yida.file.service;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.yida.common.core.exception.ServiceException;
import com.yida.common.core.utils.StringUtils;
import com.yida.common.core.utils.file.FileUtils;
import com.yida.common.security.utils.SecurityUtils;
import com.yida.file.config.MinioConfig;
import com.yida.file.domain.MinioFileInfo;
import com.yida.file.mapper.MinioFileInfoMapper;
import com.yida.file.utils.FileUploadUtils;
import com.yida.file.utils.MinioUtil;
import com.yida.system.api.domain.SysFile;
import com.yida.system.api.domain.UploadRequest;
import com.yida.system.api.domain.UploadResultDTO;
import com.alibaba.nacos.common.utils.IoUtils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;

/**
 * Minio 文件存储
 *
 * @author yida
 */
@Primary
@Service
public class MinioSysFileServiceImpl implements IMinioSysFileService {

    private static final Logger log = LoggerFactory.getLogger(IMinioSysFileService.class);

    @Autowired
    private MinioClient client;

    @Resource
    private MinioConfig minioConfig;

    @Resource
    private MinioUtil minioUtil;

    @Resource
    private MinioFileInfoMapper infoMapper;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Value("${minio.enable-dynamic-link}")
    private Boolean enableDynamicLink;

    @Value("${minio.expire-time}")
    private Integer expireTime;

    /**
     * 本地文件上传接口
     *
     * @param file 上传的文件
     * @return 访问地址
     * @throws Exception
     */
    @Override
    public String uploadFile(MultipartFile file) throws Exception {
        String fileName = FileUploadUtils.extractFilename(file);
        // 20230525临时增加
        InputStream fileInputStream = file.getInputStream();
        // PutObjectArgs args =
        // PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(fileName).stream(file.getInputStream(),
        // file.getSize(), -1).contentType(file.getContentType()).build();
        PutObjectArgs args = PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(fileName).stream(fileInputStream, file.getSize(), -1).contentType(file.getContentType()).build();
        client.putObject(args);
//        fileInputStream.close();
        //20240322变更关闭方法
        IoUtils.closeQuietly(fileInputStream);
        return minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + fileName;
    }

    @Override
    public InputStream download(String filePath, String bucket, String downloadName, HttpServletResponse response) throws Exception {
        if (!FileUtils.checkAllowDownload(filePath)) {
            throw new Exception(StringUtils.format("资源文件({})非法，不允许下载。 ", filePath));
        }
        // 从MinIo取出
        GetObjectArgs args = GetObjectArgs.builder().bucket(bucket).object(filePath).build();
        InputStream inputStream = client.getObject(args);
        // 写出至响应
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        FileUtils.setAttachmentResponseHeader(response, downloadName);
        try (ServletOutputStream os = response.getOutputStream()) {
            if (inputStream == null) {
                throw new FileNotFoundException(filePath);
            }
            byte[] b = new byte[1024];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } catch (IOException e) {
            // log.error(e.getMessage(), e);
        } finally {
            if (inputStream != null) {
//                inputStream.close();
                //20240322变更关闭方法
                IoUtils.closeQuietly(inputStream);
            }
        }
        return inputStream;
    }

    @Override
    public List<UploadResultDTO> uploadInBucket(String bucketName, List<String> fileNames, MultipartFile[] files) {
        // 文件已存在则删除 避免minio空间不足
        if (CollUtil.isNotEmpty(fileNames)) {
            fileNames.stream().forEach(filename -> {
                try {
                    minioUtil.deleteObject(bucketName, filename);
                } catch (Exception e) {
                    log.error("uploadInBucket删除旧文件失败：", e);
                    throw new ServiceException("删除旧文件失败");
                }
            });
            infoMapper.delBatch(bucketName, fileNames);
        }
        // 上传新文件
        List<UploadResultDTO> resultList = Arrays.asList(files).stream().map(file -> {
            String filename = FileUploadUtils.extractFilename(file);
            InputStream inputStream = null;
            try {
            	inputStream = file.getInputStream();
                minioUtil.uploadFile(inputStream, bucketName, filename);
            } catch (Exception e) {
                throw new ServiceException("上传文件失败！请稍后重试");
            }finally{
                if (inputStream != null) {
//                  inputStream.close();
                      //20240322变更关闭方法
                      IoUtils.closeQuietly(inputStream);
                  }
              }
            return filename;
        }).map(item -> {
            UploadResultDTO dto = new UploadResultDTO();
            dto.setBucketName(bucketName);
            dto.setFileName(item);
            return dto;
        }).collect(Collectors.toList());
        // 存储文件信息
        List<MinioFileInfo> infoList = resultList.stream().map(item -> {
            MinioFileInfo fileInfo = new MinioFileInfo() {
                /**
                 *
                 */
                private static final long serialVersionUID = -2224653382730212209L;

                {
                    setBucketName(bucketName);
                    setFileName(item.getFileName());
                    setFileUrl(minioConfig.getUrl() + "/" + bucketName + "/" + item.getFileName());
                    setCreateBy(SecurityUtils.getUsername());
                }
            };
            return fileInfo;
        }).collect(Collectors.toList());
        infoMapper.insertBatch(infoList);
        return resultList;
    }

    @Override
    public List<String> dynamicLink(UploadRequest request) {
        if (ObjUtil.isEmpty(request.getFileNames())) {
            throw new ServiceException("查询文件名不能为空！");
        }
        List<String> dynamicLinks = Collections.emptyList();
        if (enableDynamicLink) {
            // TODO 1、后期数据量大要分段 2、先走redis
            dynamicLinks = request.getFileNames().stream().map(filename -> {
                try {
                    String url = minioUtil.getPresignedObjectUrl(request.getBucketName(), filename, expireTime);

                    return url;
                } catch (Exception e) {
                    log.error("获取文件动态链接地址异常：", e);
                    throw new ServiceException("获取文件动态链接失败，请稍后重试" + e.getMessage());
                }
            }).collect(Collectors.toList());
        } else {
            // TODO 数据库数据不是顺序的 改用map<文件名,url>
            dynamicLinks = infoMapper.list(request.getBucketName(), request.getFileNames());
        }
        return dynamicLinks;
    }

    // --------------------------------------------新增功能区----------------------------------
    /* 
     * 获取文件链接地址
     */
    @Override
    public SysFile getFileInfo(UploadRequest request) {
        String bucketName = request.getBucketName();
        String fileName = request.getFileName();
        if (StringUtils.isEmpty(fileName)) {
            throw new ServiceException("查询文件名不能为空！");
        }
        SysFile sysFile = infoMapper.selectFileInfo(bucketName, fileName);
        if (StringUtils.isNull(sysFile)) {
            throw new ServiceException("未获取到对应的文件信息，请核对！");
        }
        // if (enableDynamicLink) {
        // 双向配置都为动态时获取动态
        if (request.getDynamicLinkFlag() && enableDynamicLink) {
            try {
                String url = minioUtil.getPresignedObjectUrl(bucketName, fileName, expireTime);
                sysFile.setUrl(url);
                sysFile.setDynamicLinkFlag(true);
            } catch (Exception e) {
                log.error("获取文件动态链接地址异常：", e);
                throw new ServiceException("获取文件动态链接失败，请稍后重试" + e.getMessage());
            }
        }
        return sysFile;
    }

    /* 
     * 获取文件链接地址集合
     */
    @Override
    public List<SysFile> getFileInfoList(UploadRequest request) {
        String bucketName = request.getBucketName();
        if (StringUtils.isEmpty(bucketName)) {
            throw new ServiceException("查询文件库不能为空！");
        }
        if (StringUtils.isEmpty(request.getFileNames())) {
            throw new ServiceException("查询文件名不能为空！");
        }
        // List<FileInfoDTO> dynamicLinks = new ArrayList
        List<SysFile> dynamicLinks = infoMapper.selectFileInfoList(bucketName, request.getFileNames());
        // 双向配置都为动态时获取动态
        if (request.getDynamicLinkFlag() && enableDynamicLink) {
            // TODO 1、后期数据量大要分段 2、先走redis
            // dynamicLinks = request.getFileNames().stream().map(filename -> {
            // try {
            // String url = minioUtil.getPresignedObjectUrl(request.getBucketName(), filename, expireTime);
            //
            // return url;
            // } catch (Exception e) {
            // log.error("获取文件动态链接地址异常：", e);
            // throw new ServiceException("获取文件动态链接失败，请稍后重试" + e.getMessage());
            // }
            // }).collect(Collectors.toList());
            // 动态文件处理
            dynamicLinks.stream().forEach(f -> {
                String url = "";
                try {
                    url = minioUtil.getPresignedObjectUrl(bucketName, f.getName(), expireTime);
                    f.setUrl(url);
                    f.setDynamicLinkFlag(true);
                } catch (Exception e) {
                    log.error("获取文件动态链接地址异常：", e);
                }
            });

        }
        return dynamicLinks;
    }

    /* 
     * 文件上传接口动态地址返回
     */
    // @Override
    // public SysFile uploadFileDynamicLink(MultipartFile file, String bucketName, boolean dynamicLink) throws Exception
    // {
    //
    // if (file.isEmpty()) {
    // throw new ServiceException("上传文件不能为空！");
    // }
    // String fileName = FileUploadUtils.extractFilename(file);
    // InputStream fileInputStream = file.getInputStream();
    // PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(fileInputStream,
    // file.getSize(), -1).contentType(file.getContentType()).build();
    // client.putObject(args);
    // fileInputStream.close();
    // SysFile sysFile = new SysFile();
    //
    // String url = minioConfig.getUrl() + "/" + bucketName + "/" + fileName;
    // if (dynamicLink && enableDynamicLink) {
    // try {
    // url = minioUtil.getPresignedObjectUrl(bucketName, fileName, expireTime);
    // sysFile.setDynamicLinkFlag(true);
    // } catch (Exception e) {
    // log.error("获取文件动态链接地址异常：", e);
    // throw new ServiceException("获取文件动态链接失败，请稍后重试" + e.getMessage());
    // }
    // }
    // sysFile.setName(fileName);
    // sysFile.setUrl(url);
    // return sysFile;
    // }

    /* 
     * 删除文件
     */
    @Override
    public boolean delFile(UploadRequest request) {
        String bucketName = request.getBucketName();
        String fileName = request.getFileName();
        if (StringUtils.isEmpty(bucketName) || StringUtils.isEmpty(fileName)) {
            throw new ServiceException("删除文件:桶名或文件名不能为空");
        }
        // 文件已存在则删除 避免minio空间不足
        try {
            minioUtil.deleteObject(bucketName, fileName);
        } catch (Exception e) {
            log.error("delFile删除旧文件失败：", e);
            throw new ServiceException("删除旧文件失败");
        }
        infoMapper.delFileBybucketNameAndFileName(bucketName, fileName);
        return true;
    }

    /* 
     * 文件上传接口动态地址返回
     */
    @Override
    public SysFile uploadFileDynamicLink(MultipartFile file, String bucketName, String fileName, boolean dynamicLinkFlag) throws Exception {
        if (file.isEmpty()) {
            throw new ServiceException("上传文件不能为空！");
        }

        // 判定文件名不为空，则进行删除
        if (!StringUtils.isEmpty(fileName)) {
            try {
                minioUtil.deleteObject(bucketName, fileName);
            } catch (Exception e) {
                log.error("uploadInBucket删除旧文件失败：", e);
                throw new ServiceException("删除旧文件失败");
            }
            infoMapper.delFileBybucketNameAndFileName(bucketName, fileName);
        } else {
            // 空的情况下，采用自动生成文件名，并返回
            fileName = FileUploadUtils.extractFilename(file);
        }
        InputStream fileInputStream = file.getInputStream();
        PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(fileInputStream, file.getSize(), -1).contentType(file.getContentType()).build();
        client.putObject(args);
        //fileInputStream.close();
        //20240322关闭流
        IoUtils.closeQuietly(fileInputStream);
        SysFile sysFile = new SysFile();

        // 静态地址
        String url = minioConfig.getUrl() + "/" + bucketName + "/" + fileName;
        // 添加记录（只做静态地址存储）
        infoMapper.insertFileInfo(bucketName, fileName, url);
        if (dynamicLinkFlag && enableDynamicLink) {
            try {
                // 动态地址
                url = minioUtil.getPresignedObjectUrl(bucketName, fileName, expireTime);
                sysFile.setDynamicLinkFlag(true);
            } catch (Exception e) {
                log.error("获取文件动态链接地址异常：", e);
                throw new ServiceException("获取文件动态链接失败，请稍后重试" + e.getMessage());
            }
        }
        sysFile.setName(fileName);
        sysFile.setUrl(url);
        return sysFile;
    }

}
